/**
 * 
 * Copyright 2009 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using mooseirc.dom;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace mooseirc.net
{
    /// <summary>
    /// Client is a connection to an IRC server.
    /// This handles all the particulars, 
    /// and provides way of communication to and from the server.
    /// </summary>
    /// <remarks>
    /// This is just a prototype,
    /// I'm fighting some inner termoil on how I want to approach this.
    /// The whole point of this library is to enable 'IRC' functionality in you're applications
    /// as a 'drop in'.
    /// 
    /// So while I'm tempted to keep the IRC specifics out of this,
    /// and use this only to handle the network aspects,
    /// I can't help but explore the possibility of having this also manage
    /// a DOM of sorts, enabling you guys to have a very obvious
    /// 
    /// Channel/User structure.
    /// 
    /// Guess we'll see where this goes...
    /// </remarks>
    [CLSCompliant(true)]
    public sealed class Client : object
    {
        #region [Local Attributes]

        /// <summary>
        /// Connection to the IRC Server
        /// </summary>
        Socket m_socket;

        /// <summary>
        /// Collection of channels
        /// </summary>
        ChannelList m_channelList;

        #endregion

        #region [Events]

        /// <summary>
        /// Event indicating data has come back from the server
        /// </summary>
        public event MessageHander MessageRecieved;

        #endregion

        #region [Construction]

        /// <summary>
        /// Initialize Client, and connection to the IRC server
        /// </summary>
        /// <param name="host">host address of server</param>
        /// <param name="port">port to connect to</param>
        public Client(string host, int port)
            : base()
        {
            m_channelList = new ChannelList();
            MessageRecieved += new MessageHander(onMessageRecieved);

            connect(host, port);
            return;
        }

        #endregion

        #region [socket crap]

        /// <summary>
        /// Create a new instance of a socket
        /// </summary>
        /// <returns>new socket</returns>
        private Socket newSocket()
        {
            return new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Connect socket to the IRC server
        /// </summary>
        /// <param name="host">host address of server</param>
        /// <param name="port">port number to connect to</param>
        /// <returns>true if connection was successful</returns>
        public bool connect(string host, int port)
        {
            EndPoint epRemote = new IPEndPoint(
                Dns.GetHostEntry(host).AddressList[0],
                port);
            if (m_socket != null)
                disconnect();
            m_socket = newSocket();
            m_socket.Connect(epRemote);

            if (m_socket.Connected)
            { //We can only really recieve if we connected to the server properly
                recieve();
            }

            return m_socket.Connected;
        }
		
        /// <summary>
        /// Disconnect from the server, point socket to null
        /// </summary>
        /// <returns>true if disconnect was successful</returns>
        public bool disconnect()
        {
            if (m_socket != null)
            {
                m_socket.Disconnect(false);
                m_socket = null;
            }
            return m_socket == null;
        }

        /// <summary>
        /// Send data to the server
        /// </summary>
        /// <param name="command">command to send to the server</param>
        public void send(string command)
        {
            byte[] sendBuffer = Encoding.UTF8.GetBytes(command + "\r\n"); //Appending the \r\n so you dont have to
            m_socket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, onSendComplete, sendBuffer);
            return;
        }

        /// <summary>
        /// Recieve data from the server
        /// </summary>
        private void recieve()
        {
            byte[] recieveBuffer = new byte[512];
            m_socket.BeginReceive(recieveBuffer, 0, recieveBuffer.Length, SocketFlags.None, onRecvComplete, recieveBuffer);
            return;
        }

        /// <summary>
        /// Async Send Complete, this doesnt do much.
        /// </summary>
        /// <param name="result">Async Results</param>
        private void onSendComplete(IAsyncResult result)
        {
            byte[] sendBuffer = (byte[])result.AsyncState;
            return;
        }

        /// <summary>
        /// Async Recieve Complete,
        /// This is a major driving method in this system.
        /// </summary>
        /// <param name="result">Async Result</param>
        private void onRecvComplete(IAsyncResult result)
        {
            byte[] recieveBuffer = (byte[])result.AsyncState;
            int recv = m_socket.EndReceive(result);
            string[] parsed = null;
            if (recv == 0)
            { //Return, if we havent recieved anything
                //I stuck this in as what I would have considered an 'obvious' safeguard, however I havent seen this happen yet.
                return;
            }

            //encode input, and break it down into lines
            foreach (string line in processInput(recieveBuffer, recv))
            {
                parsed = parseLine(line); //Parse Line
                dispatch(parsed); //dispatch/work based on data that has come in
            }
            recieve(); //Set up recieve again
            return;
        }

        #endregion

        #region [Logic]

        /// <summary>
        /// Breaks input down into multiple inputs.  Inputs are separated via CRLF breaks
        /// </summary>
        /// <param name="input">Data that has in</param>
        /// <param name="length">length of data recieved, as reported by the socket recieve method</param>
        /// <returns>an array of 'lines' to parse </returns>
        private string[] processInput(byte[] input, int length)
        {
            return Encoding.UTF8.GetString(input, 0, length).Split(
                new string[] { "\r\n" },
                StringSplitOptions.RemoveEmptyEntries);
        }

        /// <summary>
        /// Parse Command
        /// </summary>
        /// <param name="line">Command to parse</param>
        /// <returns>parsed line, comes back as an array.
        /// [0] is the command
        /// [1] is the arguments
        /// </returns>
        private string[] parseLine(string line)
        {
            if (line[0] == ':' && line.Length == 1) //It is possible that we get only a ':', and i've chosen to just return an empty set
                return new string[] { "", "" };

            string[] parsed = new string[2];
            if (line[0] == ':') //I peel the : off
                line = line.Substring(line.IndexOf(" ")).Trim();
            parsed[0] = line.Substring(0, line.IndexOf(" "));
            parsed[1] = line.Substring(line.IndexOf(" "));

            return parsed;
        }

        /// <summary>
        /// Dispatch action, based on command
        /// </summary>
        /// <param name="parsedLine">parsed command, [0] should be the command, [1] should be the arguments</param>
        private void dispatch(string[] parsedLine)
        {
            switch (parsedLine[0])
            {
                case "PING": //The servers keepalive request.
                    send("PONG " + parsedLine[1]); //Just send the token back
                    break;
                case "353":

                    break;
                case "366":

                    break;
                default:

                    //In reality, we will create a mechanism here that will allow this engine to lookup and cache it's handler...

                    MessageRecieved(parsedLine[0] + " " + parsedLine[1]);
                    break;
            }

            return;
        }

        #endregion

        #region [Internal Event Handlers]

        /// <summary>
        /// Internal Handler for Messages Recieved.
        /// I stuck this in here, mostly just to prevent a null reference exception, if the 
        /// client application for some reason does not handle the event.
        /// 
        /// </summary>
        /// <param name="message">Message Recieved</param>
        private void onMessageRecieved(string message)
        {
            return;
        }

        #endregion

    }
}
